---------------MicroChess--------------
A 4am crack                  2019-12-08
---------------------------------------

Name: MicroChess
Version: 2.0
Genre: board
Year: 1978
Credits: Peter Jennings
Publisher: Personal Software
Platform: Apple ][ (24K *)
Media: 5.25-inch disk
Sides: 1
OS: custom

(*) not a typo

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  works

Copy ][+ nibble editor
  pure 13-sector disk ($D5 $AA $B5 /
  $D5 $AA $AD prologues)

Disk Fixer
  can't read 13-sector disks

Copy ][+ sector editor
  DOS 3.2 mode can't read any track,
  but DOS 3.2 PATCHED mode can read
  every track. The boot sectors on
  track 0 don't appear to be 6502 code.
  Most sectors on other tracks are the
  same data over and over.

Why didn't COPYA work?
  13-sector disk

Why didn't Locksmith FDB work?
  ditto

The game is a single loader. Once it's
in memory, it never touches the disk
again.

Next steps:

  1. Trace the boot
  2. Capture the game code in memory
  3. Write it out to a standard disk
  4. Declare victory (*)

(*) go to the gym

                   ~

               Chapter 1
     The Googles, They Do Nothing


This game was released just a few
months after the original 13-sector
Disk ][ drives were invented. Needless
to say, it is a 13-sector disk without
any fancy "hybrid" bootloader that
would allow it to boot on 16-sector
drives.

Furthermore, neither Apple's BOOT13
utility nor the DOS 3.3 Basics disk --
whose entire purpose is to boot older
13-sector disks -- can boot this disk.
Which is unusual and, if I may say so,
wildly unhelpful. You had one job.

Thus, I shall proceed with an original
13-sector drive and its original 13-
sector drive controller card (under
emulation, naturally, because nothing
is real).

This drive controller, like the 16-
sector drive controller, can run from
any address with a "6" in the right
place. Thus:

*9600<C600.C6FFM

And now I can modify the boot process.

The 13-sector drive controller loads a
the boot sector from track 0, sector 0
into $0300, then jumps to the entry
point at $0301. Here are the final few
instructions of that process:

96F4-   CC 00 03    CPY   $0300
96F7-   D0 03       BNE   $96FC
96F9-   4C 01 03    JMP   $0301
96FC-   4C 2D FF    JMP   $FF2D

I can interrupt the boot at $96F9 and
gain access to the boot sector code.

*96F9:4C 59 FF

*9600G
...boots slot 6...
<beep>

*301L

; standard 13-sector boot code creating
; a data table at $0800
0301-   B9 00 08    LDA   $0800,Y
0304-   0A          ASL
0305-   0A          ASL
0306-   0A          ASL
0307-   99 00 08    STA   $0800,Y
030A-   C8          INY
030B-   D0 F4       BNE   $0301
030D-   A6 2B       LDX   $2B
030F-   A9 09       LDA   #$09
0311-   85 27       STA   $27

; this is not normal
0313-   20 C1 03    JSR   $03C1

*3C1L

; store a magic value in the I/O vector
; in zero page -- suspicious, because
; the I/O vector is 2 bytes, but we're
; only setting 1 of them, so I suspect
; this has nothing to do with the I/O
; vector at all
03C1-   A9 11       LDA   #$11
03C3-   85 38       STA   $38

; immediately wipe the value we just
; stored in zero page (from the above
; code, not from zero page)
03C5-   A9 00       LDA   #$00
03C7-   8D C2 03    STA   $03C2

; unconditional jump
03CA-   F0 01       BEQ   $03CD
03CC-   34          ???

; this is the code that would normally
; appear at $0313, which called this
; routine
03CD-   AD CC 03    LDA   $03CC
03D0-   60          RTS

OK, so we've set a magic value in zero
page, in a location (I/O vector) that
would get overwritten if we hit Reset,
then self-modified to cover our tracks.
Lovely.

Continuing from $0316...

; standard 13-sector boot code --
; ($40) points to the address where
; we're going to load the next few
; sectors
; ($3E) points to an entry point in
; the drive controller firmware to read
; those sectors
0316-   85 41       STA   $41
0318-   84 40       STY   $40
031A-   8A          TXA
031B-   4A          LSR
031C-   4A          LSR
031D-   4A          LSR
031E-   4A          LSR
031F-   09 C0       ORA   #$C0
0321-   85 3F       STA   $3F
0323-   A9 5D       LDA   #$5D
0325-   85 3E       STA   $3E

; this will read a sector
0327-   20 43 03    JSR   $0343

; this will denibble-ize it
032A-   20 46 03    JSR   $0346

; this will let us know when we're done
032D-   A5 3D       LDA   $3D
032F-   4D FF 03    EOR   $03FF
0332-   F0 06       BEQ   $033A

; if we're not done, increment the
; target memory address (high byte)
; and the target sector ID and loop
0334-   E6 41       INC   $41
0336-   E6 3D       INC   $3D
0338-   D0 ED       BNE   $0327

*3FF

03FF- 0B
*3CC

03CC- 34

This loop will read sectors into $3500-
$3FFF.

; execution continues here (from $0332)
; once all additional sectors have been
; loaded from track 0
033A-   85 3E       STA   $3E
033C-   AD CC 03    LDA   $03CC
033F-   EA          NOP

; this is not normal (we would normally
; perturb the ($3E) vector and jump to
; it in order to jump to the start of
; the code we just read)
0340-   4C A5 03    JMP   $03A5

*3A5L

; decrypt everything we just read into
; $3500-$3FFF
03A5-   A9 35       LDA   #$35
03A7-   85 49       STA   $49
03A9-   A0 00       LDY   #$00
03AB-   84 48       STY   $48
03AD-   B1 48       LDA   ($48),Y
03AF-   49 A5       EOR   #$A5
03B1-   91 48       STA   ($48),Y
03B3-   C8          INY
03B4-   D0 F7       BNE   $03AD
03B6-   E6 49       INC   $49
03B8-   A5 49       LDA   $49
03BA-   C9 40       CMP   #$40
03BC-   D0 EF       BNE   $03AD

; now jump to it
03BE-   4C 00 35    JMP   $3500

And that, once again, is where I get to
interrupt the boot.

                   ~

               Chapter 2
  How Much Sum Could A Checksum Check
    If A Checksum Could Check Some


Instead of simply breaking to the
monitor after reading the boot sector,
I want to allow it to execute -- read
all the sectors it wants to read, plus
decrypt them in memory -- then
interrupt it at $03BE instead of
jumping to $3500.

*96F9:4C 00 97

9700-   A9 59       LDA   #$59
9702-   8D BF 03    STA   $03BF
9705-   A9 FF       LDA   #$FF
9707-   8D C0 03    STA   $03C0
970A-   4C 01 03    JMP   $0301

*9600G
...reboots slot 6...
...read read read...
<beep>

Now we should have the decrypted
code at $3500-$3FFF. Let's take a look.

*3500L

; at this point, X is the boot slot x16
3500-   8E CE 35    STX   $35CE
3503-   8E C0 35    STX   $35C0

3506-   20 76 35    JSR   $3576

*3576L

; clear and display text screen
3576-   D8          CLD
3577-   20 84 FE    JSR   $FE84
357A-   20 2F FB    JSR   $FB2F
357D-   20 93 FE    JSR   $FE93
3580-   20 58 FC    JSR   $FC58

; print the title page (stored at $3600
; as a string, cleverly using embedded
; lo-bit characters as HTAB commands)
3583-   A0 00       LDY   #$00
3585-   B9 00 36    LDA   $3600,Y
3588-   30 04       BMI   $358E
358A-   85 24       STA   $24
358C-   10 07       BPL   $3595
358E-   84 F4       STY   $F4
3590-   20 F0 FD    JSR   $FDF0
3593-   A4 F4       LDY   $F4
3595-   C8          INY
3596-   D0 ED       BNE   $3585
3598-   A0 00       LDY   #$00
359A-   60          RTS

Continuing from $3509...

; set up normal DOS RWTS tables in the
; text page screen holes (these keep
; track of which disk track was last
; read)
3509-   AD C0 35    LDA   $35C0
350C-   4A          LSR
350D-   4A          LSR
350E-   4A          LSR
350F-   4A          LSR
3510-   AA          TAX
3511-   A9 00       LDA   #$00
3513-   8D 78 04    STA   $0478
3516-   9D 78 04    STA   $0478,X
3519-   9D F8 04    STA   $04F8,X

; $3700 is an array of tracks/sectors
; to read
351C-   B9 00 37    LDA   $3700,Y

*3700.

3700- 01 00 01 08 01 01 01 09
3708- 01 06 04 03 04 0B 04 04
3710- 04 0C 04 05 08 02 08 0A
3718- 08 07 08 00 08 08 0F 01
3720- 0F 09 0F 06 0F 03 0F 0B
3728- 12 04 12 0C 12 05 12 02
3730- 12 0A 12 07 16 00 16 08
3738- 16 01 16 09 00 00 00 00

It skips around the disk, reading a few
sectors on track 1, 4, 5, 8, $0F, $12,
then $16. The game itself doesn't read
from disk once it's loaded, so I don't
care about the specifics, but it's
interesting to peer into the minds of
developers making a copy protection for
a device (the Disk ][ floppy drive)
that had only existed for a few months.
There were no bit copiers yet, so this
disk was a complete black hole to all
available third-party tools.

Continuing from $351F...

; store the track in the RWTS parameter
; table
351F-   8D C3 35    STA   $35C3
3522-   C8          INY

; if both track and sector are 0, we're
; done
3523-   19 00 37    ORA   $3700,Y
3526-   F0 21       BEQ   $3549

; otherwise store the sector in the
; RWTS parameter table
3528-   B9 00 37    LDA   $3700,Y
352B-   8D C4 35    STA   $35C4
352E-   C8          INY
352F-   84 F4       STY   $F4

; this is extremely interesting --
; we're taking the track number and
; XOR'ing it with the magic value in
; zero page that the boot sector put
; there earlier, then storing it...
; somewhere?
3531-   AD C3 35    LDA   $35C3
3534-   45 38       EOR   $38
3536-   8D 8F 39    STA   $398F

To understand what just happened, we'll
go look at the code around $398F.

*398EL

398E-   A9 00       LDA   #$00      <--
3990-   85 27       STA   $27
3992-   BD 8C C0    LDA   $C08C,X
3995-   10 FB       BPL   $3992
3997-   2A          ROL
3998-   85 26       STA   $26
399A-   BD 8C C0    LDA   $C08C,X
399D-   10 FB       BPL   $399A
399F-   25 26       AND   $26
39A1-   99 2C 00    STA   $002C,Y
39A4-   45 27       EOR   $27
39A6-   88          DEY
39A7-   10 E7       BPL   $3990
39A9-   A8          TAY
39AA-   D0 B7       BNE   $3963     <--

This is the address field parser. Every
sector on a 13-sector disk (and, later,
16-sector disks) has an address field
containing disk volume, track number,
sector number, and a checksum. XOR'ing
all of those values must yield 0, or
else the sector is considered corrupt
and the read operation fails.

That "0" is what we just changed. It's
initialized at $398E and checked at
$39AA. Instead of 0, this disk requires
a different non-zero value on each
track -- and the exact value is
calculated from the magic number that
was stored in the I/O vector during
early boot.

Also, this is why BOOT13 can't boot the
original disk. Unlike the drive
controller firmware, BOOT13 insists on
verifying the address field checksum
for track 0, sector 0. Since that
checksum is intentionally corrupted, it
never finds a valid boot sector.

That's great. Copy protection is great.

                   ~

               Chapter 3
        A Checksum Would Check
       As Much As It Could Check
    If A Checksum Could Check Some


Continuing from $3539...

; read the sector
3539-   A9 35       LDA   #$35
353B-   A0 BF       LDY   #$BF
353D-   20 00 3D    JSR   $3D00
3540-   B0 59       BCS   $359B
3542-   A4 F4       LDY   $F4

; increment the target memory address
; (high byte)
3544-   EE C8 35    INC   $35C8

; this acts as an unconditional branch,
; since we'll never get as far as
; reading into $FF00 (if we even got as
; far as $3500, we would overwrite this
; code)
3547-   D0 D3       BNE   $351C

; execution continues here (from $3526)
; once the entire game is read into
; memory --
; show hi-res page 1
3549-   AD 57 C0    LDA   $C057
354C-   AD 50 C0    LDA   $C050
354F-   AD 52 C0    LDA   $C052

; decrypt and move the entire game code
; to slightly lower memory (from $0800+
; to $0200+)
3552-   A9 02       LDA   #$02
3554-   85 F1       STA   $F1
3556-   A9 08       LDA   #$08
3558-   85 F3       STA   $F3
355A-   A0 00       LDY   #$00
355C-   84 F0       STY   $F0
355E-   84 F2       STY   $F2
3560-   B1 F2       LDA   ($F2),Y
3562-   49 F0       EOR   #$F0
3564-   91 F0       STA   ($F0),Y
3566-   C8          INY
3567-   D0 F7       BNE   $3560
3569-   E6 F1       INC   $F1
356B-   E6 F3       INC   $F3
356D-   A5 F1       LDA   $F1
356F-   C9 20       CMP   #$20
3571-   D0 ED       BNE   $3560

; start the game
3573-   4C 00 06    JMP   $0600

One final boot trace should be able to
capture the entire game in memory.

; set up callback
9700-   A9 0D       LDA   #$0D
9702-   8D BF 03    STA   $03BF
9705-   A9 97       LDA   #$97
9707-   8D C0 03    STA   $03C0

; start the boot
970A-   4C 01 03    JMP   $0301

; callback is here --
; change the copy/decrypt loop so it
; decrypts the game into $4200 instead
; of $0200
970D-   A9 42       LDA   #$42
970F-   8D 53 35    STA   $3553

; stop at $6000 instead of $2000
9712-   A9 60       LDA   #$60
9714-   8D 70 35    STA   $3570

; break to monitor instead of jumping
; to game code
9717-   A9 59       LDA   #$59
9719-   8D 74 35    STA   $3574
971C-   A9 FF       LDA   #$FF
971E-   8D 75 35    STA   $3575

; continue the boot
9721-   4C 00 35    JMP   $3500

*9600G
...boots slot 6...
...read read read...
<beep>

Now we have the entire (decrypted!)
game code at $4200-$5FFF.

Rebooting to my work disk in slot 5
(which is really just a .dsk file
mounted in an emulator, because -- and
I can not stress this enough -- nothing
is real), I can save all this work. To
disk. As one does.

[S5,D1=my work disk]

*C500G
...boots slot 5...

]BSAVE OBJ.0200-1FFF,A$4200,L$1E00
]BSAVE OBJ.3500-3FFF,A$3500,L$B00

Now, a simple write routine to write
the game to a blank disk. The entire
game fits on tracks 1 and 2, with room
to spare.

; straightforward multi-sector write
; loop, via the RWTS vector at $03D9
08C0-   A9 08       LDA   #$08
08C2-   A0 E8       LDY   #$E8
08C4-   20 D9 03    JSR   $03D9
08C7-   AC ED 08    LDY   $08ED
08CA-   88          DEY
08CB-   10 05       BPL   $08D2
08CD-   A0 0F       LDY   #$0F
08CF-   CE EC 08    DEC   $08EC
08D2-   8C ED 08    STY   $08ED
08D5-   CE F1 08    DEC   $08F1
08D8-   CE E1 08    DEC   $08E1
08DB-   D0 E3       BNE   $08C0
08DD-   60          RTS

         +-- sector count
         v
08E0- 17 1D 0A 1B E8 B7 00 B4

08E8- 01 60 01 00 02 0D FB 08
                  ^   ^
          track --+   +-- sector

08F0- 00 5F 00 00 02 00 FE 60
         ^
         +-- starting address

08F8- 01 00 00 00 01 EF D8 00

*BSAVE WRITER,A$8C0,L$40

[S6,D1=blank disk]

*8C0G
...write write write...

                   ~

               Chapter 4
           Finishing Touches


Of course this disk can't boot yet,
because it has no bootloader. We'll
borrow the boot sector and RWTS from a
DOS 3.3 master disk. This has the
added benefit of loading at $3800,
in the same place the original disk
loaded its RWTS, so the cracked version
will still run on a 24K Apple ][.

[S6,D2=DOS 3.3 master]

[Disk Fixer]
  [Copy sectors 0-9 from drive 2 to 1]

I also want to reproduce the title
screen with credits during loading.
There's a free sector on T00,S05, which
is loaded at $3B00. (It's used by the
RWTS as scratch space while reading
sectors, but you can put whatever you
like there as long as you use it before
reading any sectors.) This is the
perfect place to store the title text.

There's plenty of room in a DOS-3.3-
bootloader-that-doesn't-load-DOS for
the print routine. I put it at $085D
and called it from $0845, after the
RWTS is loaded from track 0. It's
functionally identical to the routine
at $3576 on the original disk.

                 --v--

T00,S00
----------- DISASSEMBLY MODE ----------
0039:EE FE 08       INC   $08FE
003C:EE FE 08       INC   $08FE
003F:20 89 FE       JSR   $FE89
0042:20 93 FE       JSR   $FE93
0045:20 5D 08       JSR   $085D   ----+
0048:A6 2B          LDX   $2B         |
004A:6C FD 08       JMP   ($08FD)     |
...                                   |
005D:20 2F FB       JSR   $FB2F   <---+
0060:20 58 FC       JSR   $FC58
0063:A0 00          LDY   #$00
0065:B9 00 3B       LDA   $3B00,Y
0068:30 04          BMI   $006E
006A:85 24          STA   $24
006C:10 07          BPL   $0075
006E:84 F4          STY   $F4
0070:20 F0 FD       JSR   $FDF0
0073:A4 F4          LDY   $F4
0075:C8             INY
0076:D0 ED          BNE   $0065
0078:60             RTS

                 --^--

T00,S01 has some minor modifications to
load the game from tracks 1 and 2 into
$0800-$25FF, like the original disk.
After that, we can use the original
game code (minus the decryption) to
move it down to lower memory.

                 --v--

T00,S01
----------- DISASSEMBLY MODE ----------
; read game
0038:20 93 37       JSR   $3793

; reset stack
003B:A2 FF          LDX   #$FF
003D:9A             TXS

; original game code (minus decryption)
003E:AD 57 C0       LDA   $C057
0041:AD 50 C0       LDA   $C050
0044:AD 52 C0       LDA   $C052
0047:A9 02          LDA   #$02
0049:85 F1          STA   $F1
004B:A9 08          LDA   #$08
004D:85 F3          STA   $F3
004F:A0 00          LDY   #$00
0051:84 F0          STY   $F0
0053:84 F2          STY   $F2
0055:B1 F2          LDA   ($F2),Y
0057:91 F0          STA   ($F0),Y
0059:C8             INY
005A:D0 F9          BNE   $0055
005C:E6 F1          INC   $F1
005E:E6 F3          INC   $F3
0060:A5 F1          LDA   $F1
0062:C9 20          CMP   #$20
0064:D0 EF          BNE   $0055

; start game
0066:4C 00 06       JMP   $0600

                 --^--

Plenty of room left on the disk to make
an animated crack screen with sound
effects and shout outs! (I refrained.)

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 2129
------------------EOF------------------
